package org.example.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import org.example.lotterysystem.common.exception.ServiceException;
import org.example.lotterysystem.common.utils.JacksonUtil;
import org.example.lotterysystem.common.utils.MailUtil;
import org.example.lotterysystem.controller.param.DrawPrizeParam;
import org.example.lotterysystem.dao.dataobject.ActivityPrizeDO;
import org.example.lotterysystem.dao.dataobject.WinningRecordDO;
import org.example.lotterysystem.dao.mapper.ActivityPrizeMapper;
import org.example.lotterysystem.dao.mapper.WinningRecordMapper;
import org.example.lotterysystem.service.DrawPrizeService;
import org.example.lotterysystem.service.activitystatus.ActivityStatusManager;
import org.example.lotterysystem.service.dto.ConvertActivityStatusDTO;
import org.example.lotterysystem.service.enmus.ActivityPrizeStatusEnum;
import org.example.lotterysystem.service.enmus.ActivityPrizeTiersEnum;
import org.example.lotterysystem.service.enmus.ActivityStatusEnum;
import org.example.lotterysystem.service.enmus.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.example.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {
    private static final Logger logger= LoggerFactory.getLogger(MqReceiver.class);
    @Autowired
    private DrawPrizeService drawPrizeService;
    @Autowired
    private ActivityStatusManager activityStatusManager;
    @Qualifier("asyncServiceExecutor")
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    private MailUtil mailUtil;
    @Autowired
    private WinningRecordMapper winningRecordMapper;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @RabbitHandler
    public void process(Map<String,String> message){
        // 成功接收到队列中的消息
        logger.info("MQ成功接收到消息，message:{}",
                JacksonUtil.writeValueAsString(message));
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);

        //保存中奖结果
        try{
            //校验请求是否有效
            if(drawPrizeService.checkDrawPrizeParam(param)){
                return;
            }
            //状态扭转处理（有设计模式 :责任链设计模式，策略模式）
            statusConvert(param);
            //保存中奖者名单
            List<WinningRecordDO> winningRecordDOList=drawPrizeService.saveWinnerRecords(param);

            // 通知中奖者（邮箱、短信）
            // 抽奖之后的后续流程，异步（并发）处理
            syncExecute(winningRecordDOList);

        }catch (ServiceException e){
            logger.error("处理MQ异常信息{}:{}",e.getCode(),e.getMessage(),e);
            //保证事务的一致性，回滚之后让消息重发
            //这里要明白，之前那个方法加了注解，书不需要手动回滚的，当加了事务的注解执行完后，后面的方法抛出了一场则需要手动回滚。
            rollback(param);
            //保证消息重发
            throw e;

        }catch (Exception e){
            logger.error("处理MQ异常信息",e);
            //保证事务一致性
            rollback(param);
            //保证消息重发
            throw e;

        }
    }


    private void rollback(DrawPrizeParam param) {
        //回滚状态。活动，奖品，人员
        //状态是否需要回滚
        if(!statusNeedConvert(param)){
            return;
        }
        //需要回滚
        rollbackStatus(param);


        //回滚中奖者名单
        //是否需要回滚,没有值才需要回滚
        if(!winnerNeedRollback(param)){
            return;
        }
        rollbackWinner(param);
    }

    private void rollbackStatus(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO=new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(param.getWinnerList().stream().map(DrawPrizeParam.Winner::getUserId).collect(Collectors.toList()));
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);
        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    private boolean statusNeedConvert(DrawPrizeParam param) {
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());
        //判断奖品状态是否是已被抽取
        return activityPrizeDO.getStatus().equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name());
    }

    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(),param.getPrizeId());

    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        int count=winningRecordMapper.countByAPId(param.getActivityId(),param.getPrizeId());
        return count>0;
    }

    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        //邮件进行通知
        threadPoolTaskExecutor.execute(()->sendEmail(winningRecordDOList));

    }

    private void sendEmail(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)){
            logger.info("中奖列表为空，不用发邮件");
            return;
        }
        for(WinningRecordDO winningRecordDO:winningRecordDOList){
            String context = "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                    + "：" + winningRecordDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "，请尽快领 取您的奖励！";
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),
                    "中奖通知", context);
        }
    }

    private void statusConvert(DrawPrizeParam param) {
        //这里注意，下面进行的封装，是还没有写入数据库，也就是说，需要通过从数据库里面查询，进行对比状态，一样则不用更新，不一样则更新
        //这里创建一个实例，用来封装要转换的数据。
        ConvertActivityStatusDTO convertActivityStatusDTO=new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        //将目标活动设置成已经完成状态
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        //将目标奖品
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);
        //处理活动相关状态转换
        activityStatusManager.handlerEvent(convertActivityStatusDTO);

    }
}
